package com.steinwurf.hdcjoinwifi;

import ohos.net.HttpProxy;
import ohos.utils.net.Uri;
import ohos.wifi.WifiDeviceConfig;

import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

public class Proxy {
    private static final String TAG = "qinjinchuan_Proxy";

    //copy from android.util.Patterns.java start
    private static final String IP_ADDRESS_STRING =
            "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
                    + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
                    + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
                    + "|[1-9][0-9]|[0-9]))";
    public static final Pattern IP_ADDRESS = Pattern.compile(IP_ADDRESS_STRING);
    /**
     * Valid UCS characters defined in RFC 3987. Excludes space characters.
     */
    private static final String UCS_CHAR = "[" +
            "\u00A0-\uD7FF" +
            "\uF900-\uFDCF" +
            "\uFDF0-\uFFEF" +
            "\uD800\uDC00-\uD83F\uDFFD" +
            "\uD840\uDC00-\uD87F\uDFFD" +
            "\uD880\uDC00-\uD8BF\uDFFD" +
            "\uD8C0\uDC00-\uD8FF\uDFFD" +
            "\uD900\uDC00-\uD93F\uDFFD" +
            "\uD940\uDC00-\uD97F\uDFFD" +
            "\uD980\uDC00-\uD9BF\uDFFD" +
            "\uD9C0\uDC00-\uD9FF\uDFFD" +
            "\uDA00\uDC00-\uDA3F\uDFFD" +
            "\uDA40\uDC00-\uDA7F\uDFFD" +
            "\uDA80\uDC00-\uDABF\uDFFD" +
            "\uDAC0\uDC00-\uDAFF\uDFFD" +
            "\uDB00\uDC00-\uDB3F\uDFFD" +
            "\uDB44\uDC00-\uDB7F\uDFFD" +
            "&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]";
    /**
     * Valid characters for IRI label defined in RFC 3987.
     */
    private static final String LABEL_CHAR = "a-zA-Z0-9" + UCS_CHAR;

    /**
     * Valid characters for IRI TLD defined in RFC 3987.
     */
    private static final String TLD_CHAR = "a-zA-Z" + UCS_CHAR;

    /**
     * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets.
     */
    private static final String IRI_LABEL =
            "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}";
    /**
     * RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters.
     */
    private static final String PUNYCODE_TLD = "xn\\-\\-[\\w\\-]{0,58}\\w";

    private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" + ")";

    private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD;
    private static final String DOMAIN_NAME_STR = "(" + HOST_NAME + "|" + IP_ADDRESS_STRING + ")";
    public static final Pattern DOMAIN_NAME = Pattern.compile(DOMAIN_NAME_STR);

    private static final String PROTOCOL = "(?i:http|https|rtsp)://";

    /* A word boundary or end of input.  This is to stop foo.sure from matching as foo.su */
    private static final String WORD_BOUNDARY = "(?:\\b|$|^)";

    private static final String USER_INFO = "(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
            + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
            + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@";

    private static final String PORT_NUMBER = "\\:\\d{1,5}";

    private static final String PATH_AND_QUERY = "[/\\?](?:(?:[" + LABEL_CHAR
            + ";/\\?:@&=#~"  // plus optional query params
            + "\\-\\.\\+!\\*'\\(\\),_\\$])|(?:%[a-fA-F0-9]{2}))*";

    /**
     * Regular expression pattern to match most part of RFC 3987
     * Internationalized URLs, aka IRIs.
     */
    public static final Pattern WEB_URL = Pattern.compile("("
            + "("
            + "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")?"
            + "(?:" + DOMAIN_NAME_STR + ")"
            + "(?:" + PORT_NUMBER + ")?"
            + ")"
            + "(" + PATH_AND_QUERY + ")?"
            + WORD_BOUNDARY
            + ")");
    //copy from android.util.Patterns.java end

    /**
     * Returns a string containing the tokens joined by delimiters.
     *
     * @param delimiter a CharSequence that will be inserted between the tokens. If null, the string
     *                  "null" will be used as the delimiter.
     * @param tokens    an array objects to be joined. Strings will be formed from the objects by
     *                  calling object.toString(). If tokens is null, a NullPointerException will be thrown. If
     *                  tokens is empty, an empty string will be returned.
     */
    public static String join(CharSequence delimiter, Iterable tokens) {
        final Iterator<?> it = tokens.iterator();
        if (!it.hasNext()) {
            return "";
        }
        final StringBuilder sb = new StringBuilder();
        sb.append(it.next());
        while (it.hasNext()) {
            sb.append(delimiter);
            sb.append(it.next());
        }
        return sb.toString();
    }

    static HttpProxy parseProxyInfo(String host, String port, String bypass, String pacUri) throws ParseException {
        HttpProxy proxyInfo = new HttpProxy();

        if (pacUri != null) {
            if (!WEB_URL.matcher(pacUri).matches()) { // PAC URI is invalid
                throw new ParseException("Invalid PAC URL format", 0);
            }
            Logger.getLogger(TAG).log(Level.INFO, "Using proxy auto-configuration URL: " + pacUri);
        } else if (host != null && !host.isEmpty() && port != null) {
            int parsedPort;

            try {
                parsedPort = Integer.parseInt(port);
            } catch (NumberFormatException e) {
                throw new ParseException("Invalid proxy port", 0);
            }

            if (bypass != null) {
                List<String> bypassList = Arrays.asList(bypass.split(","));
                Logger.getLogger(TAG).log(Level.INFO, "Using proxy <" + host + ":" + port + ">, exclusion list: [" + join(", ", bypassList) + "]");
            } else {
                Logger.getLogger(TAG).log(Level.INFO, "Using proxy <" + host + ":" + port + ">");
            }
        } else if (host != null && port == null) {
            throw new ParseException("Proxy host specified, but missing port", 0);
        }

        // If all values were null, proxyInfo is null
        return proxyInfo;
    }

    static void setProxy(WifiDeviceConfig wfc, HttpProxy proxyInfo) throws IllegalArgumentException, ReflectiveOperationException {
        // This method is used since WifiConfiguration.setHttpProxy() isn't supported below sdk v.26

        // Code below adapted from
        //   https://stackoverflow.com/a/33949339/660982
        Class proxySettings = Class.forName("ohos.wifi.IpInfo$ProxySettings");

        Class[] setProxyParams = new Class[2];
        setProxyParams[0] = proxySettings;
        setProxyParams[1] = HttpProxy.class;

        Method setProxy = wfc.getClass().getDeclaredMethod("setProxy", setProxyParams);
        setProxy.setAccessible(true);

        Object[] methodParams = new Object[2];

        // Define methodParams[0] (proxy type: NONE, STATIC, or PAC)
        if (proxyInfo == null) {
            methodParams[0] = Enum.valueOf(proxySettings, "NONE");
        } else {
            // Double check that ProxyInfo is valid
            Method isValid = proxyInfo.getClass().getDeclaredMethod("isValid");
            isValid.setAccessible(true);
            boolean proxyInfoIsValid = (boolean) isValid.invoke(proxyInfo);

            if (!proxyInfoIsValid) {
                throw new IllegalArgumentException("Proxy settings are not valid");
            }

            if (!Uri.EMPTY_URI.equals(proxyInfo.getConfigUrl())) {
                methodParams[0] = Enum.valueOf(proxySettings, "PAC");
            } else if (proxyInfo.getHost() != null && proxyInfo.getPort() != 0) {
                methodParams[0] = Enum.valueOf(proxySettings, "STATIC");
            } else {
                methodParams[0] = Enum.valueOf(proxySettings, "NONE");
            }
        }

        // Define methodParams[1] (proxy connection info)
        methodParams[1] = proxyInfo;
        setProxy.invoke(wfc, methodParams);
    }
}
